/* Copyright (c) 2021, LiWangQian<liwangqian@huawei.com> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#pragma once

#include "cpplua/config.h"
#include "cpplua/utils/range.h"
#include "fmt/format.h"

#include <exception>

CPPLUA_NS_BEGIN

enum parser_error_t {
    Unexpected = 0,
    Expected,
    ExpectedToken,
    UnfinishedString,
    MalformedNumber,
    InvalidVar,
    DecimalEscapeTooLarge,
    InvalidEscape
};

namespace error {

struct syntax_error : public std::exception {
    string_t message;
    std::size_t line;
    std::size_t column;
    std::size_t offset;

    syntax_error(const syntax_error &e) noexcept
        : message{e.message}, line{e.line}, column{e.column}, offset{e.offset}
    {
        // nop
    }

    syntax_error &operator=(const syntax_error &e) noexcept
    {
        if (&e != this) {
            message = e.message;
            line = e.line;
            column = e.column;
            offset = e.offset;
        }

        return *this;
    }

    syntax_error(string_t msg, const position_t &p)
        : message{std::move(msg)}, line{p.line}, column{p.column}, offset{p.offset}
    {
    }

    const char *what() const noexcept override
    {
        return message.c_str();
    }
};

struct unexpected : public syntax_error {
    unexpected(const position_t &p, const char *type, const char *found, const char *near_)
        : syntax_error(fmt::format("[{}:{}] unexpected {} '{}' near '{}'", p.line, p.column, type, found, near_), p)
    {
    }

    unexpected(const position_t &p, const string_t &type, const string_t &found, const string_t &near_)
        : unexpected(p, type.c_str(), found.c_str(), near_.c_str())
    {
    }
};

struct expected : public syntax_error {
    expected(const position_t &p, const char *expect, const char *near_)
        : syntax_error(fmt::format("[{}:{}] '{}' expected near '{}'", p.line, p.column, expect, near_), p)
    {
    }

    expected(const position_t &p, const string_t &expect, const string_t &near_)
        : expected(p, expect.c_str(), near_.c_str())
    {
    }
};

struct unexpected_token : public syntax_error {
    unexpected_token(const position_t &p, const char *type, const char *found)
        : syntax_error(fmt::format("[{}:{}] unexpected {} near '{}'", p.line, p.column, type, found), p)
    {
    }

    unexpected_token(const position_t &p, const string_t &type, const string_t &found)
        : unexpected_token(p, type.c_str(), found.c_str())
    {
    }
};

struct unfinished_string : public syntax_error {
    unfinished_string(const position_t &p, const char *near_)
        : syntax_error(fmt::format("[{}:{}] unfinished string near '{}'", p.line, p.column, near_), p)
    {
    }

    unfinished_string(const position_t &p, const string_t &near_)
        : unfinished_string(p, near_.c_str())
    {
    }
};

struct unfinished_long_string : public syntax_error {
    unfinished_long_string(const position_t &p, const char *near_)
        : syntax_error(fmt::format("[{}:{}] unfinished long string near '{}'", p.line, p.column, near_), p)
    {
    }

    unfinished_long_string(const position_t &p, const string_t &near_)
        : unfinished_long_string(p, near_.c_str())
    {
    }
};

struct unfinished_long_comment : public syntax_error {
    unfinished_long_comment(const position_t &p, const char *near_)
        : syntax_error(fmt::format("[{}:{}] unfinished long comment near '{}'", p.line, p.column, near_), p)
    {
    }

    unfinished_long_comment(const position_t &p, const string_t &near_)
        : unfinished_long_comment(p, near_.c_str())
    {
    }
};

struct malformed_number : public syntax_error {
    malformed_number(const position_t &p, const char *near_)
        : syntax_error(fmt::format("[{}:{}] malformed number near '{}'", p.line, p.column, near_), p)
    {
    }

    malformed_number(const position_t &p, const string_t &near_)
        : malformed_number(p, near_.c_str())
    {
    }
};

} // namespace error

CPPLUA_NS_END
